#include "Entity.hpp"

using namespace std;

// utilitary functions to compute normal vector in case
// of a Off file import

glm::vec3 vec3NormalComp(int p1, int p2, int p3, float* coords) {
          glm::vec3 v1(coords[3*p1],coords[3*p1+1],coords[3*p1+2]);
          glm::vec3 v2(coords[3*p2],coords[3*p2+1],coords[3*p2+2]);
          glm::vec3 v3(coords[3*p3],coords[3*p3+1],coords[3*p3+2]);
          return (cross(v1-v2,v3-v2));
}

glm::vec3 vec3NormalComp(int p1, int p2, int p3, int p4, float* coords) {
          glm::vec3 v1(coords[3*p1],coords[3*p1+1],coords[3*p1+2]);
          glm::vec3 v2(coords[3*p2],coords[3*p2+1],coords[3*p2+2]);
          glm::vec3 v3(coords[3*p3],coords[3*p3+1],coords[3*p3+2]);
          glm::vec3 v4(coords[3*p4],coords[3*p4+1],coords[3*p4+2]);
          return (cross(v1-v2,v3-v2));
}

void Entity::importFromMFFile(string filename_, float scaleFactor) {
	std::cout << "loading file : " << filename_ << " ... ";//<< " width scale factor = " << scaleFactor << "." << std::endl;
	const char* filename = filename_.c_str();
    char* tmp = new char[100];
    fstream filestr;
    filestr.open(filename,fstream::in);
    filestr.getline(tmp,100); // deleting comment line TODO 100 ?
    filestr.getline(tmp,100); // deleting comment line TODO 100 ?
	// retrieving version
	string comment;
	int versionNumber;
	filestr >> comment >> versionNumber;
    filestr >> nb_points >> nb_faces;// >> param;
    filestr.getline(tmp,100); // TODO 100 ?
    //float points[nb_points][3];
    // parsage des points de l'objet
    m_points = new float[3*nb_points];//.reserve(3*nb_points);
    m_pointNormals = new float[3*nb_points];
    int idpt;
    for (int i = 0; i < nb_points; i++) {
      filestr >> idpt;
      filestr >> m_points[3*i+0] >> m_points[3*i+1] >> m_points[3*i+2];
      filestr >> m_pointNormals[3*i+0] >> m_pointNormals[3*i+1] >> m_pointNormals[3*i+2];
    filestr.getline(tmp,100); // TODO 100 ?
      
    };
	// si la version est supérieure strictement à 1, le fichier peut contenir une description de la HitBox
    // creation of UV coords array
    // 3 points par face et 2 coords UV par point
    m_triangleUVcoords = new float[nb_faces * 3 * 2];
    // faces analysis
    filestr.getline(tmp,100); // TODO 100 ?
    for (int i = 0; i < nb_faces; i++) {
        // nombres de points de la face
        int n; filestr >> n;
        if (n != 3) std::cout << " ERROR : l'exporter de Blender Mesh ne fonctionne qu'avec des triangles.\n";
        // parsage des coords des normales
        float normTmp[3];
        filestr >> normTmp[0] >> normTmp[1] >> normTmp[2];
        m_triangleNormals.push_back(glm::vec3(normTmp[0],normTmp[1],normTmp[2]));
        int smooth; filestr >> smooth;
        if (smooth > 0) m_faceSmoothFlag.push_back(true);
        else m_faceSmoothFlag.push_back(false);
        // parsage des UV coords
        int idp; 
        for (int j = 0; j < 3; j++) {
          filestr >> idp;
          m_triangles.push_back(idp);
          float uvtmp;
          filestr >> uvtmp;
          m_triangleUVcoords[6*i + 2*j] = uvtmp;
          filestr >> uvtmp;
          m_triangleUVcoords[6*i + 2*j + 1] = uvtmp;
        }
    }
	// application du facteur d'échelle
    for (int i = 0; i < nb_points; i++) {
    	m_points[3*i] *= scaleFactor;
	    m_points[3*i+1] *= scaleFactor;
    	m_points[3*i+2] *= scaleFactor;
	}
    // creation de la hitbox 
	if (versionNumber < 2) {
	    glm::vec3 corner;
    	glm::vec3 dimension(0,0,0);
	    if (nb_points > 0) {
    		corner.x = m_points[0];
		    corner.y = m_points[1];
		    corner.z = m_points[2];
    	};
    	// application du facteur d'échelle
    	for (int i = 0; i < nb_points; i++) {
      		if (corner.x > m_points[3*i]) corner.x = m_points[3*i];
      		if (corner.y > m_points[3*i+1]) corner.y = m_points[3*i+1];
      		if (corner.z > m_points[3*i+2]) corner.z = m_points[3*i+2];
      		if (m_points[3*i] - corner.x > dimension.x) dimension.x = m_points[3*i] - corner.x;
      		if (m_points[3*i+1] - corner.y > dimension.y) dimension.y = m_points[3*i+1] - corner.y;
      		if (m_points[3*i+2] - corner.z > dimension.z) dimension.z = m_points[3*i+2] - corner.z;
    	};
    	glm::vec3 dim1(dimension.x,0,0);
    	glm::vec3 dim2(0,dimension.y,0);
    	glm::vec3 dim3(0,0,dimension.z);
    	m_hitboxes = new HitBoxes();
		m_hitboxes->addHitBox(HitBox(corner,dim1,dim2,dim3));
	} else {
		// on consomme la ligne laissée avant la description de la HitBox
    	filestr.getline(tmp,100); // TODO 100 ?
		string desc;
		filestr >> desc;
		if (desc != "hitbox") {
			cout << "DEBUG : hitbox not found in .mf file  " << endl;
			m_hitboxes = NULL;
		} else if (desc == "hitbox") {
			cout << "DEBUG : hitbox found in .mf file  " << endl;
			int hitboxVertexNumber, hitboxFaceNumber;
			filestr >> hitboxVertexNumber >> hitboxFaceNumber;
    		filestr.getline(tmp,100); // TODO 100 ?
			// hitboxes declaration
			m_hitboxes = new HitBoxes();
			m_hitboxes->init(hitboxVertexNumber);


			// vertex parsing
			float* vertices = new float[3*hitboxVertexNumber];
			for (int i = 0; i < hitboxVertexNumber;i++) {
				int tmp;
				filestr >> tmp >> vertices[3*i] >> vertices[3*i+1] >> vertices[3*i+2];
				vertices[3*i] *= scaleFactor;
				vertices[3*i+1] *= scaleFactor;
				vertices[3*i+2] *= scaleFactor;
			};
			m_hitboxes->setCoords(vertices);
			// faces parsing and hitbox computation
			map<int,int> vertexBox;
			for (int i = 0; i < hitboxVertexNumber;i++) vertexBox[i] = i;
				
			// consuming line between vertices and faces
    		filestr.getline(tmp,100); // TODO 100 ?
			for (int i = 0; i < hitboxFaceNumber; i++) {
				int tmp,v1,v2,v3,v4;
				filestr >> tmp >> v1 >> v2 >> v3 >> v4;
				m_hitboxes->addLink(v1,v2);
				m_hitboxes->addLink(v2,v3);
				m_hitboxes->addLink(v3,v4);
				m_hitboxes->addLink(v4,v1);
			};
			m_hitboxes->createHitBox();
			m_hitboxes->getSelection(glm::mat4(1.0f))->print();
	
		};
	
	}
	// creation des buffers de données
	createBuffer();
	std::cout << " DONE." << std::endl;
}

void Entity::createBuffer() {
  // array declaration
  int elementSize = 8;
  GLfloat* array = new GLfloat[m_triangles.size()*elementSize];
  int arraySize = m_triangles.size() * elementSize * sizeof(GLfloat); 
  int indexArraySize = m_triangles.size() * sizeof(GLuint); 
  GLuint* indexArray = new GLuint[m_triangles.size()];
  m_buffers = new GLuint[2];
  glGenBuffers(2,m_buffers);
  // array filling
  for (unsigned int i = 0; i < m_triangles.size();i++) {
    indexArray[i] = i;//m_triangles[i];
    array[elementSize*i] = m_points[3*m_triangles[i]];
    array[elementSize*i+1] = m_points[3*m_triangles[i]+1];
    array[elementSize*i+2] = m_points[3*m_triangles[i]+2];
    float normal[3];
    if (not m_faceSmoothFlag[(i /3)]) {
      normal[0] = m_triangleNormals[(i/3)].x; 
      normal[1] = m_triangleNormals[(i/3)].y; 
      normal[2] = m_triangleNormals[(i/3)].z; 
    } else {
      for (int j = 0; j < 3;j++) normal[j] = m_pointNormals[3*m_triangles[i]+j];
    };
    // allocation de la normale
    for (int j = 0; j < 3; j++) array[elementSize*i+3+j] = normal[j];
    array[elementSize*i+6] = m_triangleUVcoords[2*i];
    array[elementSize*i+7] = m_triangleUVcoords[2*i+1];
  };
  glBindBuffer(GL_ARRAY_BUFFER,m_buffers[0]);
  glBufferData(GL_ARRAY_BUFFER,arraySize,array,GL_STATIC_DRAW);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,m_buffers[1]);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER,indexArraySize,indexArray,GL_STATIC_DRAW);

  glBindBuffer(GL_ARRAY_BUFFER,0);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
  
} 



void Entity::importFromOffFile(char* fichier, float scaleFactor) {
    char* tmp = new char[100];
    fstream filestr;
    filestr.open(fichier,fstream::in);
    filestr.getline(tmp,100); // TODO 100 ?
    filestr >> nb_points >> nb_faces >> param;
    //float points[nb_points][3];
    // parsage des points de l'objet
    m_points = new float[3*nb_points];//.reserve(3*nb_points);
    for (int i = 0; i < nb_points; i++) {
      filestr >> m_points[3*i+0] >> m_points[3*i+1] >> m_points[3*i+2];
    };
    for (int i = 0; i < nb_points; i++) {
      m_points[3*i] *= scaleFactor;
      m_points[3*i+1] *= scaleFactor;
      m_points[3*i+2] *= scaleFactor;
    };
    // faces analysis
    for (int i = 0; i < nb_faces; i++) {
        // nombres de points de la face
        int n; filestr >> n;
        int idp;
        for (int j = 0; j < n; j++) {
            filestr >> idp;
            if (n == 3) m_triangles.push_back(idp);
            else if (n == 4) m_quads.push_back(idp);
            else std::cout << "le moteur ne gère pour l'instant que les triangles et les quads" << std::endl;
        }
    }
    // calcul des normales des triangles
    for (unsigned int i = 0; i < m_triangles.size() / 3; i++)
      m_triangleNormals.push_back(vec3NormalComp(m_triangles[3*i],m_triangles[3*i+1],m_triangles[3*i+2],m_points));
    // calcul des normales des quads
    for (unsigned int i = 0; i < m_quads.size() / 4; i++)
      m_quadNormals.push_back(vec3NormalComp(m_quads[4*i],m_quads[4*i+1],m_quads[4*i+2],m_quads[4*i+3],m_points));
    if (m_quads.size() > 0) cout << "calcul de normale non prévu pour les quads" << endl;
}




void Entity::drawDirectly() {
	if (m_material) m_material->use();
	glBegin(GL_TRIANGLES);
	glm::vec3 tmp;
	for (unsigned int i = 0; i < m_triangles.size() / 3; i++) {
		int i1,i2,i3;
		i1 = (m_triangles[3*i]);  
		i2 = (m_triangles[3*i+1]); 
		i3 = (m_triangles[3*i+2]); 
		float* p1 = m_points+3*i1;
		float* p2 = m_points+3*i2;
		float* p3 = m_points+3*i3; 
		tmp = m_triangleNormals[i];
		bool flag = m_faceSmoothFlag[i];
		if (not flag) glNormal3f(tmp.x,tmp.y,tmp.z); 
		float* pt= m_triangleUVcoords + 6 *i; 
		if (flag) glNormal3fv(m_pointNormals+3*i1);
		glTexCoord2f(pt[0],pt[1]);
		glVertex3fv(p1);
		if (flag) glNormal3fv(m_pointNormals+3*i2);
		glTexCoord2f(pt[2],pt[3]);
		glVertex3fv(p2);
		if (flag) glNormal3fv(m_pointNormals+3*i3);
		glTexCoord2f(pt[4],pt[5]);
		glVertex3fv(p3);	
	};
	glEnd();
	if (m_material) m_material->unuse();
}

void Entity::drawByBuffer() {
	glColor3f(1.0f,0.0f,0.0f);
	if (m_material) m_material->use();
	glBindBuffer(GL_ARRAY_BUFFER,m_buffers[0]);
	glVertexPointer(3,GL_FLOAT,8*sizeof(GLfloat),0); 
	glEnableClientState(GL_VERTEX_ARRAY);

	glNormalPointer(GL_FLOAT,8*sizeof(GLfloat),BUFFER_OFFSET(3*sizeof(GLfloat)));
	glEnableClientState(GL_NORMAL_ARRAY);

	glTexCoordPointer(2,GL_FLOAT,8*sizeof(GLfloat),BUFFER_OFFSET(6*sizeof(GLfloat)));
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	glDrawArrays(GL_TRIANGLES,0,m_triangles.size());
	if (m_material) m_material->unuse();
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);
}


map<string,Entity*> EntityManager::m_entityMap;


Entity* EntityManager::getEntity(string name, string filename_, float scaleFactor) {
	const char* filename = filename_.c_str();
  map<string,Entity*>::iterator it = m_entityMap.find(name);
  if (it != m_entityMap.end()) return (*it).second;
  // si l'entité n'existe pas on va la créée 
  else {
    Entity* pEntity = new Entity();
    pEntity->importFromMFFile(filename,scaleFactor);
    m_entityMap[name] = pEntity;
    return pEntity;
  };
  return 0;
};


